/*
 * jsPlumb
 *
 * Title:jsPlumb 1.4.1
 *
 * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
 * elements, or VML.
 *
 * This file contains the basic adapter that does not require any support library such as jquery, yui or mootools.

	but it's not yet there.  currently this is a copy of the jquery adapter.

 *
 * Copyright (c) 2010 - 2013 Simon Porritt (http://jsplumb.org)
 *
 * http://jsplumb.org
 * http://github.com/sporritt/jsplumb
 * http://code.google.com/p/jsplumb
 *
 * Dual licensed under the MIT and GPL2 licenses.
 */
/*
 * the library specific functions, such as find offset, get id, get attribute, extend etc.
 * the full list is:
 *
 * addClass				adds a class to the given element
 * animate				calls the underlying library's animate functionality
 * appendElement		appends a child element to a parent element.
 * bind					binds some event to an element
 * dragEvents			a dictionary of event names
 * extend				extend some js object with another.  probably not overly necessary; jsPlumb could just do this internally.
 * getAttribute			gets some attribute from an element
 * getDragObject		gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
 * getDragScope			gets the drag scope for a given element.
 * getDropScope			gets the drop scope for a given element.
 * getElementObject		turns an id or dom element into an element object of the underlying library's type.
 * getOffset			gets an element's offset
 * getOriginalEvent     gets the original browser event from some wrapper event
 * getPageXY			gets the page event's xy location.
 * getParent			gets the parent of some element.
 * getScrollLeft		gets an element's scroll left.  TODO: is this actually used?  will it be?
 * getScrollTop			gets an element's scroll top.  TODO: is this actually used?  will it be?
 * getSize				gets an element's size.
 * getUIPosition		gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
 * hasClass				returns whether or not the given element has the given class.
 * initDraggable		initializes an element to be draggable
 * initDroppable		initializes an element to be droppable
 * isDragSupported		returns whether or not drag is supported for some element.
 * isDropSupported		returns whether or not drop is supported for some element.
 * removeClass			removes a class from a given element.
 * removeElement		removes some element completely from the DOM.
 * setAttribute			sets an attribute on some element.
 * setDraggable			sets whether or not some element should be draggable.
 * setDragScope			sets the drag scope for a given element.
 * setOffset			sets the offset of some element.
 * trigger				triggers some event on an element.
 * unbind				unbinds some listener from some element.
 */
(function ($) {

    //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement;

    var _setClassName = function (el, val) {
            if (el.className.baseVal)
                el.className.baseVal = val;
            else
                el.className = val;
        },
        _getClassName = function (el) {
            return el.className.baseVal != null ? el.className.baseVal : el.className;
        },
        _classManip = function (el, add, clazz) {
            var classesToAddOrRemove = clazz.split(" "),
                curClasses = _getClassName(el).split(" ");

            for (var i = 0; i < classesToAddOrRemove.length; i++) {
                if (add) {
                    if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
                        curClasses.push(classesToAddOrRemove[i]);
                } else {
                    var idx = curClasses.indexOf(classesToAddOrRemove[i]);
                    if (idx != -1)
                        curClasses.splice(idx, 1);
                }
            }

            _setClassName(el, curClasses.join(" "));
        },
        _addClass = function (el, clazz) {
            _classManip(el, true, clazz);
        },
        _removeClass = function (el, clazz) {
            _classManip(el, false, clazz);
        };


    jsPlumb.CurrentLibrary = {

        /**
         * adds the given class to the element object.
         */
        addClass: function (el, clazz) {
            el = jsPlumb.CurrentLibrary.getElementObject(el);
            try {
                if (el[0].className.constructor == SVGAnimatedString) {
                    jsPlumbUtil.svg.addClass(el[0], clazz);
                }
            } catch (e) {
                // SVGAnimatedString not supported; no problem.
            }
            try {
                el.addClass(clazz);
            } catch (e) {
                // you probably have jQuery 1.9 and Firefox.
            }
        },

        /**
         * animates the given element.
         */
        animate: function (el, properties, options) {
            el.animate(properties, options);
        },

        /**
         * appends the given child to the given parent.
         */
        appendElement: function (child, parent) {
            jsPlumb.CurrentLibrary.getElementObject(parent).append(child);
        },

        /**
         * executes an ajax call.
         */
        ajax: function (params) {
            params = params || {};
            params.type = params.type || "get";
            $.ajax(params);
        },

        /**
         * event binding wrapper.  it just so happens that jQuery uses 'bind' also.  yui3, for example,
         * uses 'on'.
         */
        bind: function (el, event, callback) {
            el = jsPlumb.CurrentLibrary.getElementObject(el);
            el.bind(event, callback);
        },

        /**
         * mapping of drag events for jQuery
         */
        dragEvents: {
            'start': 'start', 'stop': 'stop', 'drag': 'drag', 'step': 'step',
            'over': 'over', 'out': 'out', 'drop': 'drop', 'complete': 'complete'
        },

        /**
         * wrapper around the library's 'extend' functionality (which it hopefully has.
         * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
         * instead.  it's not like its hard.
         */
        extend: function (o1, o2) {
            return $.extend(o1, o2);
        },

        /**
         * gets the named attribute from the given element object.
         */
        getAttribute: function (el, attName) {
            return el.attr(attName);
        },

        getClientXY: function (eventObject) {
            return [eventObject.clientX, eventObject.clientY];
        },

        /**
         * takes the args passed to an event function and returns you an object representing that which is being dragged.
         */
        getDragObject: function (eventArgs) {
            return eventArgs[1].draggable || eventArgs[1].helper;
        },

        getDragScope: function (el) {
            return el.draggable("option", "scope");
        },

        getDropEvent: function (args) {
            return args[0];
        },

        getDropScope: function (el) {
            return el.droppable("option", "scope");
        },

        /**
         * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById),
         * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other
         * two cases).  this is the opposite of getElementObject below.
         */
        getDOMElement: function (el) {
            if (typeof (el) == "string") return document.getElementById(el);
            else if (el.context || el.length != null) return el[0];
            else return el;
        },

        /**
         * gets an "element object" from the given input.  this means an object that is used by the
         * underlying library on which jsPlumb is running.  'el' may already be one of these objects,
         * in which case it is returned as-is.  otherwise, 'el' is a String, the library's lookup
         * function is used to find the element, using the given String as the element's id.
         *
         */
        getElementObject: function (el) {
            return typeof (el) == "string" ? $("#" + el) : $(el);
        },

        /**
         * gets the offset for the element object.  this should return a js object like this:
         *
         * { left:xxx, top: xxx }
         */
        getOffset: function (el) {
            return el.offset();
        },

        getOriginalEvent: function (e) {
            return e.originalEvent;
        },

        getPageXY: function (eventObject) {
            return [eventObject.pageX, eventObject.pageY];
        },

        getParent: function (el) {
            return jsPlumb.CurrentLibrary.getElementObject(el).parent();
        },

        getScrollLeft: function (el) {
            return el.scrollLeft();
        },

        getScrollTop: function (el) {
            return el.scrollTop();
        },

        getSelector: function (context, spec) {
            if (arguments.length == 2)
                return jsPlumb.CurrentLibrary.getElementObject(context).find(spec);
            else
                return $(context);
        },

        /**
         * gets the size for the element object, in an array : [ width, height ].
         */
        getSize: function (el) {
            return [el.outerWidth(), el.outerHeight()];
        },

        getTagName: function (el) {
            var e = jsPlumb.CurrentLibrary.getElementObject(el);
            return e.length > 0 ? e[0].tagName : null;
        },

        /**
         * takes the args passed to an event function and returns you an object that gives the
         * position of the object being moved, as a js object with the same params as the result of
         * getOffset, ie: { left: xxx, top: xxx }.
         *
         * different libraries have different signatures for their event callbacks.
         * see getDragObject as well
         */
        getUIPosition: function (eventArgs, zoom) {

            zoom = zoom || 1;
            // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes
            // in the wrong offset if the element has a margin (it doesn't take the margin into account).  the getBoundingClientRect
            // method, which is in pretty much all browsers now, reports the right numbers.  but it introduces a noticeable lag, which
            // i don't like.

            /*if ( getBoundingClientRectSupported ) {
                var r = eventArgs[1].helper[0].getBoundingClientRect();
                return { left : r.left, top: r.top };
            } else {*/
            if (eventArgs.length == 1) {
                ret = {left: eventArgs[0].pageX, top: eventArgs[0].pageY};
            } else {
                var ui = eventArgs[1],
                    _offset = ui.offset;

                ret = _offset || ui.absolutePosition;

                // adjust ui position to account for zoom, because jquery ui does not do this.
                ui.position.left /= zoom;
                ui.position.top /= zoom;
            }
            return {left: ret.left / zoom, top: ret.top / zoom};
        },

        hasClass: function (el, clazz) {
            return el.hasClass(clazz);
        },

        /**
         * initialises the given element to be draggable.
         */
        initDraggable: function (el, options, isPlumbedComponent) {
            options = options || {};
            // remove helper directive if present and no override
            if (!options.doNotRemoveHelper)
                options.helper = null;
            if (isPlumbedComponent)
                options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
            el.draggable(options);
        },

        /**
         * initialises the given element to be droppable.
         */
        initDroppable: function (el, options) {
            options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
            el.droppable(options);
        },

        isAlreadyDraggable: function (el) {
            el = jsPlumb.CurrentLibrary.getElementObject(el);
            return el.hasClass("ui-draggable");
        },

        /**
         * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
         */
        isDragSupported: function (el, options) {
            return el.draggable;
        },

        /**
         * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
         */
        isDropSupported: function (el, options) {
            return el.droppable;
        },

        /**
         * removes the given class from the element object.
         */
        removeClass: function (el, clazz) {
            el = jsPlumb.CurrentLibrary.getElementObject(el);
            try {
                if (el[0].className.constructor == SVGAnimatedString) {
                    jsPlumbUtil.svg.removeClass(el[0], clazz);
                    return;
                }
            } catch (e) {
                // SVGAnimatedString not supported; no problem.
            }
            el.removeClass(clazz);
        },

        removeElement: function (element) {
            jsPlumb.CurrentLibrary.getElementObject(element).remove();
        },

        /**
         * sets the named attribute on the given element object.
         */
        setAttribute: function (el, attName, attValue) {
            el.attr(attName, attValue);
        },

        /**
         * sets the draggable state for the given element
         */
        setDraggable: function (el, draggable) {
            el.draggable("option", "disabled", !draggable);
        },

        /**
         * sets the drag scope.  probably time for a setDragOption method (roll this and the one above together)
         * @param el
         * @param scope
         */
        setDragScope: function (el, scope) {
            el.draggable("option", "scope", scope);
        },

        setOffset: function (el, o) {
            jsPlumb.CurrentLibrary.getElementObject(el).offset(o);
        },

        /**
         * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
         * the other libraries do not.  yui, in fact, cannot even pass an original event.  we have to pull out stuff
         * from the originalEvent to put in an options object for YUI.
         * @param el
         * @param event
         * @param originalEvent
         */
        trigger: function (el, event, originalEvent) {
            //originalEvent.stopPropagation();
            //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent);
            var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle");
            h(originalEvent);
            //originalEvent.stopPropagation();
        },

        /**
         * event unbinding wrapper.  it just so happens that jQuery uses 'unbind' also.  yui3, for example,
         * uses..something else.
         */
        unbind: function (el, event, callback) {
            el = jsPlumb.CurrentLibrary.getElementObject(el);
            el.unbind(event, callback);
        }
    };

    $(document).ready(jsPlumb.init);

})(jQuery);
